package ace.module.captcha.core.impl.manager.impl;

import ace.cmp.core.utils.SystemClockUtils;
import ace.cmp.data.spring.data.jdbc.querydsl.rsql.core.manager.spring.AbstractSpringDtoManager;
import ace.cmp.i18n.api.MessageSourceAssertService;
import ace.cmp.id.generator.api.LongIdGenerator;
import ace.module.captcha.api.model.dto.CaptchaItemDto;
import ace.module.captcha.core.impl.dao.entity.CaptchaApplication;
import ace.module.captcha.core.impl.dao.entity.CaptchaItem;
import ace.module.captcha.core.impl.dao.entity.CaptchaItemConfig;
import ace.module.captcha.core.impl.dao.repository.CaptchaApplicationRepository;
import ace.module.captcha.core.impl.manager.CaptchaApplicationManager;
import ace.module.captcha.core.impl.manager.CaptchaItemManager;
import ace.module.captcha.core.impl.manager.repository.CaptchaItemConfigMgrRepository;
import ace.module.captcha.core.impl.manager.repository.CaptchaItemMgrRepository;
import ace.module.captcha.core.impl.properties.CaptchaCoreProperties;
import java.time.Instant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;

/**
 * 验证码 spring data jdbc querydsl rsql manager implement.
 *
 * @author Caspar
 */
@Slf4j
@Component
public class CaptchaItemManagerImpl
    extends AbstractSpringDtoManager<CaptchaItem, Long, CaptchaItemDto>
    implements CaptchaItemManager {

  private static final String MSG_CODE_ERROR = "验证码错误";
  private static final String MSG_CONFIG_DISABLED_ERROR = "该验证类目已禁用,请联系系统管理员";
  private static final Integer INIT_ATTEMPT_COUNT = 0;
  private final CaptchaItemMgrRepository captchaItemMgrRepository;
  private final CaptchaItemConfigMgrRepository captchaItemConfigMgrRepository;
  private final CaptchaApplicationRepository captchaApplicationRepository;
  private final CaptchaApplicationManager captchaApplicationManager;
  private final LongIdGenerator idGenerator;
  private final CaptchaCoreProperties properties;
  private final CaptchaItemConfig defaultCaptchaItemConfig;
  private final MessageSourceAssertService assertService;

  public CaptchaItemManagerImpl(
      CaptchaItemMgrRepository captchaItemMgrRepository,
      CaptchaItemConfigMgrRepository captchaItemConfigMgrRepository,
      CaptchaApplicationRepository captchaApplicationRepository,
      CaptchaApplicationManager captchaApplicationManager,
      LongIdGenerator idGenerator,
      CaptchaCoreProperties properties,
      MessageSourceAssertService assertService) {
    this.captchaItemMgrRepository = captchaItemMgrRepository;
    this.captchaItemConfigMgrRepository = captchaItemConfigMgrRepository;
    this.captchaApplicationRepository = captchaApplicationRepository;
    this.captchaApplicationManager = captchaApplicationManager;
    this.idGenerator = idGenerator;
    this.properties = properties;
    this.assertService = assertService;
    CaptchaCoreProperties.CaptchaItemConfigProperties captchaItemConfigProperties =
        properties.getDefaultCaptchaItemConfig();
    this.defaultCaptchaItemConfig =
        CaptchaItemConfig.builder()
            .enable(captchaItemConfigProperties.getEnable())
            .systemCode(captchaItemConfigProperties.getSystemCaptcha())
            .enableSystemCode(captchaItemConfigProperties.getEnableSystemCaptcha())
            .expireInSeconds(captchaItemConfigProperties.getExpireInSeconds())
            .attemptCount(captchaItemConfigProperties.getAttemptCount())
            .build();
  }

  /**
   * 验证验证码是否正确
   */
  @Override
  public void verify(Long captchaItemId, String code) {

    CaptchaItem captchaItem = this.captchaItemMgrRepository.getById(captchaItemId);

    assertService.isNull(captchaItem, CaptchaItem.class.getName() + ".not-exist", "验证码不存在");

    CaptchaItemConfig itemConfig =
        this.captchaItemConfigMgrRepository.getById(captchaItem.getCaptchaAppId());

    assertService.isNull(itemConfig, CaptchaItemConfig.class.getName() + ".not-exist", "验证码配置不存在");

    if (itemConfig.getEnable() == null || itemConfig.getEnable() == false) {
      return;
    }

    // 万能验证码
    if (itemConfig.getEnableSystemCode() == true
        && StringUtils.isNotEmpty(itemConfig.getSystemCode())
        && StringUtils.equalsIgnoreCase(itemConfig.getSystemCode(), code)) {
      this.captchaItemMgrRepository.remove(captchaItemId);
      return;
    }

    // 验证次数
    assertService.isTrue(
        captchaItem.getAttemptCount() >= itemConfig.getAttemptCount(),
        CaptchaItem.class.getName() + ".expire-error",
        "验证码已过期");

    // 过期时间
    assertService.isTrue(
        SystemClockUtils.currentTimeMillis() > captchaItem.getExpireTime(),
        CaptchaItem.class.getName() + ".expire-error",
        "验证码已过期");

    if (!StringUtils.equalsIgnoreCase(captchaItem.getCode(), code)) {
      captchaItem.setAttemptCount(captchaItem.getAttemptCount() + 1);
      captchaItem.setUpdateTime(SystemClockUtils.currentTimeMillis());
      captchaItem.setRowVersion(captchaItem.getRowVersion() + 1);
      this.captchaItemMgrRepository.save(captchaItem);
      assertService.isTrue(Boolean.TRUE, CaptchaItem.class.getName() + ".expire-error", "验证码已过期");
    }
    this.captchaItemMgrRepository.remove(captchaItem.getId());
  }

  @Override
  public CaptchaItem getById(Long captchaItemId) {
    CaptchaItem captchaItem = this.captchaItemMgrRepository.getById(captchaItemId);
    return captchaItem;
  }

  @Override
  public CaptchaItem create(CaptchaApplication captchaApplication, String code) {
    Long captchaApplicationId = captchaApplication.getId();

    CaptchaItemConfig config =
        this.getCaptchaItemConfigOrDefaultByCaptchaApplicationBizTypeId(captchaApplicationId);

    assertService.isFalse(
        config.getEnable(), "captcha.captcha-item-config.is-disabled", MSG_CONFIG_DISABLED_ERROR);

    Instant createTime = Instant.now();
    Instant updateTime = Instant.now();

    Instant expireTime = createTime.plusSeconds(config.getExpireInSeconds());

    CaptchaItem captchaItem =
        CaptchaItem.builder()
            .id(idGenerator.get())
            .captchaAppId(captchaApplicationId)
            .attemptCount(INIT_ATTEMPT_COUNT)
            .expireTime(expireTime.toEpochMilli())
            .code(code)
            .createTime(createTime.toEpochMilli())
            .updateTime(updateTime.toEpochMilli())
            .rowVersion(null)
            .build();

    this.captchaItemMgrRepository.save(captchaItem);

    return captchaItem;
  }

  private CaptchaItemConfig getCaptchaItemConfigOrDefaultByCaptchaApplicationBizTypeId(
      Long appBizTypeId) {
    CaptchaItemConfig config = this.captchaItemConfigMgrRepository.getById(appBizTypeId);

    if (config != null) {
      return config;
    }

    return this.defaultCaptchaItemConfig;
  }

  /**
   * 删除服务端验证码
   */
  @Override
  public boolean remove(Long id) {
    this.captchaItemMgrRepository.remove(id);
    return true;
  }
}
